/* first draw the laser */
/* then draw the rest of the world */
/* detect laser colisions as objects are being drawn */
/* if the laser colides with any object do a laser explosion next cycle and change the object to dying */
/* then draw the bombs */
/* detect colisions with the bases or the laser cannon */

#include <hpgcc49.h>
#include "invaders.h"
#include "sprite.h"

static unsigned char RTC_VALUE;

/*
 * unsafe
 */
#define MIN(A,B) ((A) < (B) ? (A) : (B))
#define MAX(A,B) ((A) > (B) ? (A) : (B))

/*
 * Apply a sprite to a buffer using XOR
 *
 * sprite is a pointer to the sprite
 *
 * sWidth is the sprite width in bytes
 * sHeight is the sprite height in pixels
 *
 * xpos is the starting x position on the buffer
 * ypos is the starting y position on the buffer
 *
 * xrcrop limits the right hand boundry for the sprite
 * xlcrop limits the left hand boundry for the sprite
 * ytcrop limits the top boundry for the sprite
 * ybcrop limits the bottom boundry for the sprite
 *
 * buf is a pointer to position 0,0 on the buffer
 * bw is the width of the buffer in bytes
 * bh is the height of the buffer
 *
 * This whole thing really sucks. It prances all over memory, uses too many
 * variables, and sometimes demons fly ouf of my nose when I do bit shifts.
 * It seems to work ok though.
 * 
 */
int SpriteWrite(
		unsigned char *sprite
		, int sWidth
		, int sHeight
		, int xpos
		, int ypos
		, int xlcrop
		, int xrcrop
		, int ytcrop
		, int ybcrop
		, unsigned char *buf
		, int bw
		, int bh
		)
{
	union u_data {
		unsigned short s;
		unsigned char  c[2];
	} data;
	
	unsigned char  *buf_pos;
	int i;
	int x;
	int align;
	int colision = 0;

	

	if (sprite == NULL || buf == NULL) return 0;

	xlcrop = MAX(0, xlcrop);  /* calculate the left side limit */
	xrcrop = MIN(xrcrop - 8, bw * 8 - 8); /* calculate the right side limit */
	ytcrop = MAX(0, ytcrop); /* calculate the top limit */
	ybcrop = MIN(bh - 1, ybcrop); /* calculate the botom limit */


	align = ((unsigned int)xpos) % 8u; /* calculate the left/right shift */

	while (sHeight--) {
		if (ypos > ybcrop) break; /* limit the sprite bottom */
		if (ypos < ytcrop) { /* limit the sprite top */
			ypos++;
			sprite += sWidth;
			continue;
		}

		buf_pos = buf + xpos / 8 + bw * ypos; /* calculate where to start writing */
		x = xpos;

		for (i = 0; i < sWidth; i++) {
			data.s = *sprite++;

			/*
			 * Limit the sprite on the right
			 */
			if (x > xrcrop)
				*data.c = *data.c & (0xff >> (x - xrcrop));

			/*
			 * Limit the sprite on the left
			 */
			if (x < xlcrop)
				*data.c = *data.c & (0xff << (xlcrop - x));

			/*
			 * Write the data
			 */
			data.s = data.s << align;
			if (x >= 0) {
				if ((*buf_pos | data.c[0]) != (*buf_pos ^ data.c[0])) {
					data.s = 0xffff;
					colision++;
				}
				*buf_pos = *buf_pos ^ data.c[0];
				buf_pos++;
			}
			if ((*buf_pos | data.c[1]) != (*buf_pos ^ data.c[1])) {
				data.s = 0xffff;
				colision++;			
			}
			*buf_pos = *buf_pos ^ data.c[1];

			x += 8;
		}
		ypos++;
	}
	return colision;
}

void * mymalloc(size_t size)
{
	void *tmp;

	if (NULL == (tmp = malloc(size))) {
		exit(1);
	}
	return tmp;
}

/*
 * Allocate and a world
 */
WORLD * world_allocate(void)
{
	int i;
	int j;
	int x;
	int y;
	WORLD *world;
	ALIEN_TYPE *alien_type;

	/*
	 * Init the base world
	 */
	world         = mymalloc(sizeof(WORLD));
	world->canvas = mymalloc(SCREEN_SIZE); /* 20BX80 131bx80 usable */
	world->screen = (unsigned char *)getLCDPointer(0,0);
	world->saucer = mymalloc(sizeof(SAUCER));
	world->aliens = mymalloc(sizeof(ALIENS));
	world->base   = mymalloc(sizeof(BASE) * NUM_BASES);
	world->cannon = mymalloc(sizeof(CANNON));
	world->laser  = mymalloc(sizeof(LASER));
	world->bomb   = mymalloc(sizeof(BOMB) * NUM_BOMBS);
	world->lives  = 0;
	world->score  = 0;
	world->tick   = 0;

	/*
	 * Init the canvas
	 */
	memset((void *)world->canvas, 0, SCREEN_SIZE);

	/*
	 * Init the saucer
	 */
	world->saucer->sprite    = SAUCER_SPRITE;
	world->saucer->x         = SAUCER_X_START_LEFT;
	world->saucer->direction = LEFT;
	world->saucer->status    = DEAD;

	/*
	 * Init the aliens
	 */
	alien_type = mymalloc(sizeof(ALIEN_TYPE) * ALIEN_TYPES);
	for (i = 0; i < ALIEN_TYPES; i++) {
		alien_type[i].sprite    = mymalloc(sizeof(SPRITE *) * 2);
		alien_type[i].sprite[0] = ALIEN_SPRITE[i][0];
		alien_type[i].sprite[1] = ALIEN_SPRITE[i][1];
		alien_type[i].width     = ALIEN_WIDTH[i];
		alien_type[i].value     = ALIEN_VALUE[i];
	}

	world->aliens->alien_row = mymalloc(sizeof(ALIEN_ROW) * ALIEN_ROWS);
	world->aliens->direction = RIGHT;
	world->aliens->speed = 77;
	world->aliens->current_row = 0;

	y = 42;
	for (i = 0; i < ALIEN_ROWS; i++) {
		world->aliens->alien_row[i].alien = mymalloc(sizeof(ALIEN) * ALIEN_COLUMNS);
		world->aliens->alien_row[i].alien_type = &alien_type[ALIEN_ROW_TYPES[i]];
		world->aliens->alien_row[i].appearance = 0;
		world->aliens->alien_row[i].tick = 5;

		if (i < 2)
			x = 9;
		else if (i < 4)
			x = 10;
		else
			x = 11;

		for (j = 0; j < ALIEN_COLUMNS; j++) {
			world->aliens->alien_row[i].alien[j].x = x;
			world->aliens->alien_row[i].alien[j].y = y;
			world->aliens->alien_row[i].alien[j].status = LIVE;
			x += ALIEN_HSPACE;
		}
		y -= ALIEN_VSPACE;
	}

	/*
	 * Init the bases
	 */
	for (i = 0; i < NUM_BASES; i++) {
		world->base[i].sprite = mymalloc(sizeof(SPRITE) * SIZEOF_BASE);
		world->base[i].x      = BASE_X[i];
		world->base[i].y      = BASE_Y;

		/* memset((void *)world->base[i].sprite, 0, SIZEOF_BASE); */
		memcpy(world->base[i].sprite, BASE_SPRITE, SIZEOF_BASE);
	}

	/*
	 * Init the cannon
	 */
	world->cannon->sprite = CANNON_SPRITE;
	world->cannon->x      = CANNON_LMAX;
	world->cannon->status = LIVE;

	/*
	 * Init the laser
	 */
	world->laser->sprite = LASER_SPRITE;
	world->laser->x      = 0;
	world->laser->y      = 0;
	world->laser->count  = 0;
	world->laser->status = DEAD;

	/*
	 * Init the bombs
	 */
	x = 56;
	y = 5;
	for (i = 0; i < NUM_BOMBS; i++) {
		world->bomb[i].sprite = mymalloc(sizeof(SPRITE *) * 2);
		world->bomb[i].sprite[0] = BOMB_SPRITE[0];
		world->bomb[i].sprite[1] = BOMB_SPRITE[1];
		world->bomb[i].appearance = i % 2;
		world->bomb[i].speed = 1;
		world->bomb[i].x = x;
		world->bomb[i].y = y;
		world->bomb[i].status = DEAD;
		y += 5;
	}

	return world;
}


void world_init(WORLD *world)
{
	return;
}

void draw_startup_screen(void)
{
	clearScreen();
}

void setup_rtc(void)
{
	RTC_VALUE = sys_setRTCTickPeriod(1); 
}

void reset_rtc()
{
	sys_setRTCTickPeriod(RTC_VALUE); 
}

/*
 * Check if the aliens should travel down
 * If so, set the direction to one of the
 * down directions.
 * If the down direction is set, set direction to right or left
 */
void set_aliens_down(WORLD * world) {
	int i;
	int j;
	int start;
	int end;
	int incr;
	int xmove;
	int xloc;

	start = 0; /* shut up compiler */
	end = 0; /* shut up compiler */
	incr = 0; /* shut up compiler */
	xmove = 0; /* shut up compiler */

	switch(world->aliens->direction) {
		case RDOWN:
			world->aliens->direction = LEFT;
			return;
			break;
		case LDOWN:
			world->aliens->direction = RIGHT;
			return;
			break;
		case RIGHT:
			start = ALIEN_COLUMNS - 1;
			end = -1;
			incr = -1;
			xmove = ALIEN_HSHIFT;
			break;
		case LEFT:
			start = 0;
			end = ALIEN_COLUMNS;
			incr = 1;
			xmove = ALIEN_HSHIFT * -1;
			break;
	}

	for (i = 0; i < ALIEN_ROWS; i++) {
		for (j = start; j != end; j += incr) {
			if (world->aliens->alien_row[i].alien[j].status == LIVE) {
				xloc = world->aliens->alien_row[i].alien[j].x + xmove; 
				if (xloc < SCREEN_LCROP || SCREEN_RCROP < xloc + world->aliens->alien_row[i].alien_type->width) {
					switch(world->aliens->direction) {
						case RIGHT:
							world->aliens->direction = RDOWN;
							return;
							break;
						case LEFT:
							world->aliens->direction = LDOWN;
							return;
							break;
						case RDOWN:
							break;
						case LDOWN:
							break;
					}
					break;
				}
			}
		}
	}
}

void move_alien_row(WORLD *world) {
	int i;
	int xmove;
	int ymove;

	xmove = 0; /* shut up compiler */
	ymove = 0; /* shut up compiler */

	switch(world->aliens->direction) {
		case RIGHT:
			xmove = ALIEN_HSHIFT;
			ymove = 0;
			break;
		case LEFT:
			xmove = ALIEN_HSHIFT * -1;
			ymove = 0;
			break;
		case RDOWN:
		case LDOWN:
			xmove = 0;
			ymove = ALIEN_VSHIFT;
			break;
	}

	world->aliens->alien_row[world->aliens->current_row].appearance = ! world->aliens->alien_row[world->aliens->current_row].appearance;
	for (i = 0; i < ALIEN_COLUMNS; i++) {
		world->aliens->alien_row[world->aliens->current_row].alien[i].x = world->aliens->alien_row[world->aliens->current_row].alien[i].x + xmove;
		world->aliens->alien_row[world->aliens->current_row].alien[i].y = world->aliens->alien_row[world->aliens->current_row].alien[i].y + ymove;
	}
}

int draw_number(WORLD *world, int n, int x, int y)
{
	int i;
	int pos;

	if (n < 0) {
		SpriteWrite(NUMBER_SPRITE[10]
			  , NUMBER_SWIDTH, NUMBER_HEIGHT
			  , x, 0
			  , SCREEN_LCROP, SCREEN_RCROP
			  , SCREEN_TCROP, SCREEN_BCROP
			  , world->canvas
			  , SCREEN_WIDTH, SCREEN_HEIGHT
			  );

		n = n * -1;
		x = x + NUMBER_WIDTH + 1;
	}

	i = n % 10;
	n = n / 10;

	if (n > 0)
		pos = draw_number(world, n, x, y) + 1;
	else
		pos = 0;

	SpriteWrite(NUMBER_SPRITE[i]
		  , NUMBER_SWIDTH, NUMBER_HEIGHT
		  , x + pos * (NUMBER_WIDTH + 1), 0
		  , SCREEN_LCROP, SCREEN_RCROP
		  , SCREEN_TCROP, SCREEN_BCROP
		  , world->canvas
		  , SCREEN_WIDTH, SCREEN_HEIGHT
		  );

	return pos;
}

START world_play(WORLD * world)
{
	STATUS status;
	SPRITE *sp;
	int i;
	int j;
int n;
	status = PLAY;
	sp = NULL; /* shut up compiler */

n = 0;
	while (status == PLAY) {

		while (!sys_isRTCTick()) {
			/* figgure out how to process sound here */
		}
		world->tick++;

		/*
		 * Laser collision detection
		 */
		if (world->laser->status == LIVE) {
			/*
			 * Aliens
			 */
			for (i = 0; i < ALIEN_ROWS; i++) {
				if ( world->aliens->alien_row[i].alien[0].y <= world->laser->y + LASER_HEIGHT - 1
				  && world->laser->y <= world->aliens->alien_row[i].alien[0].y + ALIEN_HEIGHT - 1
				  ) {
					for (j = 0; j < ALIEN_COLUMNS; j++) {
						if (world->aliens->alien_row[i].alien[j].status == LIVE
						 && world->aliens->alien_row[i].alien[j].x <= world->laser->x + LASER_WIDTH - 1
						 && world->laser->x <= world->aliens->alien_row[i].alien[j].x + world->aliens->alien_row[i].alien_type->width - 1
						) {
							world->laser->status = DEAD;
							world->aliens->alien_row[i].alien[j].status = DEAD;
							world->score += world->aliens->alien_row[i].alien_type->value;
							world->aliens->speed = world->aliens->speed - (158 - world->aliens->speed) * 18 / 1000;
							goto LASER_HIT;
						}
					}
				}
			}

			/*
			 * Saucer
			 */ 
			if (world->saucer->status == LIVE
			 && SAUCER_Y <= world->laser->y + LASER_HEIGHT - 1
			 && world->laser->y <= SAUCER_Y + SAUCER_HEIGHT - 1
			 && world->saucer->x <= world->laser->x + LASER_WIDTH - 1
			 && world->laser->x <= world->saucer->x + SAUCER_WIDTH - 1
			 ) {
				world->saucer->status = DEAD;
				world->score += SAUCER_VALUE[world->laser->count % SAUCER_VALUE_SZ];
			 	goto LASER_HIT;
			 }
		}
LASER_HIT:

		/*
		 * Saucer motion/launch
		 */
		if (world->tick % 3 == 0) {
			if (world->saucer->status == LIVE) {
				world->saucer->x += world->saucer->direction;

				if (world->saucer->x < SAUCER_X_START_RIGHT || SAUCER_X_START_LEFT < world->saucer->x) {
					world->saucer->status = DEAD;
				}
			} else {
				i = rand() % 50000;
				if (i < world->aliens->speed) {
					i = rand() % 2;
					if (i == 0) {
						world->saucer->direction = RIGHT;
						world->saucer->x = SAUCER_X_START_RIGHT;
					} else {
						world->saucer->direction = LEFT;
						world->saucer->x = SAUCER_X_START_LEFT;
					}
					world->saucer->status = LIVE;
				}
			}
		}
		/*
		 * Alien motion
		 */
		world->aliens->alien_row[world->aliens->current_row].tick--;
		if (world->aliens->alien_row[world->aliens->current_row].tick <= 0) {
			if (world->aliens->current_row == 0) {
				set_aliens_down(world);
				world->aliens->alien_row[world->aliens->current_row].tick = world->aliens->speed;
			} else {
				world->aliens->alien_row[world->aliens->current_row].tick = world->aliens->speed * 65 / 1000;
			}

			move_alien_row(world);

			world->aliens->current_row++;
			if (world->aliens->current_row == ALIEN_ROWS) {
				world->aliens->current_row = 0;
			}
		}

		/*
		 * Cannon motion
		 */
		if (world->tick % 2 == 0) {
			if (keyb_isKeyPressed(6,3) && world->cannon->x <= CANNON_RMAX) { /* right direction key */
				world->cannon->x++;
			} else if (keyb_isKeyPressed(6,1) && CANNON_LMAX <= world->cannon->x) { /* left direction key */
				world->cannon->x--;
			}
		}

		/*
		 * Laser motion and bounds detection
		 */
		if (world->laser->status == LIVE) {
			world->laser->y--;

			if (world->laser->y < SCREEN_TCROP) {
				world->laser->status = DEAD;
			}
		}


		/*
		 * Fire laser cannon
		 */
		if (world->laser->status != LIVE && keyb_isKeyPressed(6,6)) { /* var key */
			rand(); /* mix it up a bit since we did not use srand */
			world->laser->count++;
			world->laser->status = LIVE;
			world->laser->x = world->cannon->x + 4;
			world->laser->y = CANNON_Y - LASER_HEIGHT;
		}

		/*
		 * Draw the world
		 */
		memset((void *)world->canvas, 0, SCREEN_SIZE);

		/*
		 * Draw the saucer
		 */
		switch(world->saucer->status) {
			case DEAD:
				sp = NULL;
				break;
			case LIVE:
				sp = world->saucer->sprite;
				break;
			case DYING:
				sp = SAUCER_DYING_SPRITE;
				break;
		}
		n += SpriteWrite(sp
			  , SAUCER_SWIDTH, SAUCER_HEIGHT
			  , world->saucer->x, SAUCER_Y
			  , SCREEN_LCROP, SCREEN_RCROP
			  , SCREEN_TCROP, SCREEN_BCROP
			  , world->canvas
			  , SCREEN_WIDTH, SCREEN_HEIGHT
			  );
		/*
		 * Draw the aliens
		 */
		for (i = 0; i < ALIEN_ROWS; i++) {
			for (j = 0; j < ALIEN_COLUMNS; j++) {
				switch (world->aliens->alien_row[i].alien[j].status) {
				case LIVE:
					sp = world->aliens->alien_row[i].alien_type->sprite[world->aliens->alien_row[i].appearance];
					break;
				case DEAD:
					continue;
					break;
				case DYING:
					sp = ALIEN_DYING_SPRITE;
					break;
				}

				n += SpriteWrite(sp
					  , ALIEN_SWIDTH, ALIEN_HEIGHT
					  , world->aliens->alien_row[i].alien[j].x
					  , world->aliens->alien_row[i].alien[j].y
					  , SCREEN_LCROP, SCREEN_RCROP
					  , SCREEN_TCROP, SCREEN_BCROP
					  , world->canvas
					  , SCREEN_WIDTH, SCREEN_HEIGHT
					  );
			}
		}

		/*
		 * Draw the bases
		 */
		for (i = 0; i < NUM_BASES; i++) {
			n += SpriteWrite(world->base[i].sprite
				  , BASE_SWIDTH, BASE_HEIGHT
				  , world->base[i].x, world->base[i].y
				  , SCREEN_LCROP, SCREEN_RCROP
				  , SCREEN_TCROP, SCREEN_BCROP
				  , world->canvas
				  , SCREEN_WIDTH, SCREEN_HEIGHT
				  );
		}

		/*
		 * Draw the cannon
		 */
		switch(world->cannon->status) {
			case LIVE:
				sp = world->cannon->sprite;
				break;
			case DEAD:
				sp = NULL;
				break;
			case DYING:
				sp = CANNON_DYING_SPRITE;
				break;
		}
		n += SpriteWrite(sp
			  , CANNON_SWIDTH, CANNON_HEIGHT
			  , world->cannon->x, CANNON_Y
			  , SCREEN_LCROP, SCREEN_RCROP
			  , SCREEN_TCROP, SCREEN_BCROP
			  , world->canvas
			  , SCREEN_WIDTH, SCREEN_HEIGHT
			  );

		/*
		 * Draw the laser
		 */
		if (world->laser->status == LIVE)
			n += SpriteWrite(world->laser->sprite
				  , LASER_SWIDTH, LASER_HEIGHT
				  , world->laser->x, world->laser->y
				  , SCREEN_LCROP, SCREEN_RCROP
				  , SCREEN_TCROP, SCREEN_BCROP
				  , world->canvas
				  , SCREEN_WIDTH, SCREEN_HEIGHT
				  );

		/*
		 * Draw the bombs
		 */
		for (i = 0; i < NUM_BOMBS; i++) {
			if (world->bomb[i].status == LIVE) {
				n += SpriteWrite(world->bomb[i].sprite[world->bomb->appearance]
					  , BOMB_SWIDTH, BOMB_HEIGHT
					  , world->bomb->x, world->bomb->y
					  , SCREEN_LCROP, SCREEN_RCROP
					  , SCREEN_TCROP, SCREEN_BCROP
					  , world->canvas
					  , SCREEN_WIDTH, SCREEN_HEIGHT
					  );
			}
		}

		/*
		 * Draw the score
		 */
		/* draw_number(world, world->score, 0, 0); */
draw_number(world, n, 0, 0);

		/*
		 * Transfer the canvas to the screen
		 */
		/* sys_LCDSynch(); */
		memcpy(world->screen, world->canvas, SCREEN_SIZE);

		if (keyb_isON())
			status = QUIT;
	}

	return status;
}

int main (void)
{
	START start;
	WORLD *world;

	setup_rtc();
	atexit(reset_rtc);

	world = world_allocate();

	start = SELECT;
	while (start != QUIT) {
		draw_startup_screen();
		if (keyb_isON()) start = QUIT;
		else if (keyb_isKeyPressed(6,6)) { /* the var key */
			while (keyb_isKeyPressed(6,6)); /* wait for the var key to come up */
			world_init(world);
			start = world_play(world);
		}
	} 

	return 0;
}
